package com.yb.core.aop;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.yb.core.annotation.Desensitize;
import com.yb.core.annotation.repeatable.Desensitizes;
import com.yb.core.bean.Resp;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Copyright (C), 2022-2022, 姚兵
 * Author: 32210
 * Date: 2022/12/9 16:06
 * FileName: DesensitizationAspect
 * Description:
 */
@Aspect
@Configuration
@EnableAspectJAutoProxy
@Slf4j
public class DesensitizeAspect {

    @Around("execution(public * com.yb.*..controller.*.*(..)) && (@annotation(com.yb.core.annotation.Desensitize) || @annotation(com.yb.core.annotation.repeatable.Desensitizes))")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        Annotation[] annotations = method.getAnnotations();
        Arrays.stream(annotations).forEach(item->{
            log.info("annotation:{}",item.toString());
        });

        Object result = joinPoint.proceed();
        
        return handleResult(result,method);
    }


    private Object handleResult(Object result,Method method){
        if(result instanceof String){
            return result;
        }

        if(method.isAnnotationPresent(Desensitizes.class)){
            this.handle(result,method.getAnnotation(Desensitizes.class));
        }else if(method.isAnnotationPresent(Desensitize.class)){
            this.handle(result,method.getAnnotation(Desensitize.class));
        }

        return result;
    }

    private void handle(Object result, Desensitizes desensitizes){
        Desensitize[] values = desensitizes.value();
        Arrays.stream(values).forEach(item->this.handle(result,item));
    }

    private void handle(Object result, Desensitize desensitize){

        try {
            setFields(result, desensitize);
        } catch (Exception e){
            log.error("{}",e);
        }

    }

    private void setFields(Object result, Desensitize desensitize) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {

        if(ClassUtil.isBasicType(result.getClass())){
            return;
        }

        if(!(result instanceof Resp)){
            return;
        }

        Resp resp= (Resp) result;

        Object data = resp.getData();
        
        if(data instanceof String){
            return;
        }

        Set<String> fields = Arrays.stream(desensitize.fields()).collect(Collectors.toSet());

        setFields(data, desensitize,fields);

    }

    private void setMap(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        log.info("object is Map");
        Map map= (Map) object;
        Set<Map.Entry> entrySet = map.entrySet();
        Iterator<Map.Entry> iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Map.Entry next =  iterator.next();
            Object key = next.getKey();
            Object value = next.getValue();

            if(!fields.contains(key))continue;
            if(Objects.isNull(value))continue;

            if(value instanceof String){
                map.put(key,DesensitizedUtil.desensitized((CharSequence) value, desensitize.type()));
            }

            setFields(value, desensitize,fields);
        }
        return;
    }

    private void setSet(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        log.info("object is set");
        Set set= (Set) object;
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object value = iterator.next();
            if(Objects.isNull(value))continue;
            setFields(value, desensitize,fields);
        }
        return;
    }


    private void setList(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        List list= (List) object;
        for (Object item:list) {
            setFields(item, desensitize,fields);
        }
    }

    private void setArray(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        Class<?> componentType = object.getClass().getComponentType();
        int length = Array.getLength(object);
        for(int i=0;i<length;i++){
            if(ClassUtil.isBasicType(componentType))continue;
            setFields(Array.get(object,i), desensitize,fields);
        }
    }

    private void setObject(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        log.info("object is Object");
        BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());

        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        // 遍历属性列表，查找指定的属性
        if (propertyDescriptors != null && propertyDescriptors.length > 0) {
            for (PropertyDescriptor propDesc : propertyDescriptors) {
                if(propDesc.getPropertyType().equals(String.class)){
                    if (fields.contains(propDesc.getName())) {
                        Method methodSetUserName = propDesc.getWriteMethod();
                        Method readMethod = propDesc.getReadMethod();
                        Object value = readMethod.invoke(object);
                        if(Objects.nonNull(value)) {
                            methodSetUserName.invoke(object, DesensitizedUtil.desensitized((CharSequence) value, desensitize.type()));  // 写入属性值
                        }
                        log.info("set {}",propDesc.getName());
                        break;
                    }
                }else{
                    setFields(ClassUtil.getDeclaredField(object.getClass(),propDesc.getName()), desensitize,fields);
                }
                // 找到则写入属性值
            }
        }
    }

    private void setFields(Object object, Desensitize desensitize, Set<String> fields) throws IntrospectionException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {

        if(Objects.isNull(object))return;

        if(ClassUtil.isBasicType(object.getClass())){
            return;
        }

        if(object instanceof Map){
            this.setMap(object, desensitize,fields);
            return;
        }

        if(object instanceof Set){
            this.setSet(object, desensitize,fields);
        }

        if(object instanceof List){
            this.setList(object, desensitize,fields);
            return;
        }

        if(object.getClass().isArray()){
            this.setArray(object, desensitize,fields);
            return;
        }

        this.setObject(object, desensitize,fields);

    }

}
